﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Threading;

namespace FileSharing
{
    public partial class Main : Form
    {
        private class SearchResult
        {
            public string Name;
            public int Availability;
            public string Type;
            public string SizeString;
            public Int64 Size;
            public byte[] Hash;
            public List<RemoteFile> Sources = new List<RemoteFile>();
        };

        List<SearchResult> SortedResults;

        private Usuario Cliente;

        public Main()
        {
            Cliente = new Usuario(this);

            InitializeComponent();
        }

        private void Main_Load(object sender, EventArgs e)
        {
            Settings sett = Settings.Instance();

            if (sett.GetValue(Settings.Setting.RandomizeIncomingPort) == "True")
            {
                Random rndPort = new Random();

                sett.SetValue(Settings.Setting.IncomingPort, (rndPort.Next() % 40000 + 10000).ToString());

                sett.Save();
            }

            Log.Instance().RegisterOutput(this);

            // Open Incoming folder
            bool ValidFolder = true;

            try
            {
                Cliente.Diretorio = sett.GetValue(Settings.Setting.IncomingDirectory);

                listStatus.Items[4].Text = "Files: " + Cliente.FilesCount.ToString();
            }
            catch (DirectoryNotFoundException ex)
            {
                ValidFolder = false;
            }

            while (ValidFolder == false)
            {
                try
                {
                    fbwIncoming.Description = "Select a valid folder for incoming files.";
                    DialogResult result = fbwIncoming.ShowDialog();

                    if (result == DialogResult.OK)
                    {
                        Cliente.Diretorio = fbwIncoming.SelectedPath;
                        sett.SetValue(Settings.Setting.IncomingDirectory, fbwIncoming.SelectedPath);
                    }
                    else
                    {
                        this.Close();
                    }

                    ValidFolder = true;
                }
                catch (FileNotFoundException ex)
                {
                    MessageBox.Show("Invalid folder.", "Incoming Folder");
                }
            }
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            if (btnConnect.Text == "Connect")
            {
                String ConnMethod = Settings.Instance().GetValue(Settings.Setting.ConnectionMethod);

                if (ConnMethod == "DefaultUser")
                {
                    Cliente.Connect();
                }
                else
                {
                    List<Vizinho> Users = new List<Vizinho>();
                    String KnownUsers = Settings.Instance().GetValue(Settings.Setting.KnownUsers);

                    foreach (String User in KnownUsers.Split(';'))
                    {
                        if (User.Length == 0)
                            continue;

                        String[] UserData = User.Split(',');

                        Vizinho viz = new Vizinho();
                        viz.Name = UserData[0];
                        viz.IP = new IPEndPoint(IPAddress.Parse(UserData[1]), Convert.ToInt32(UserData[2]));

                        Users.Add(viz);
                    }

                    Cliente.Connect(Users.ToArray());
                }

                TimerVizinhos.Interval = 500;
                TimerVizinhos.Start();

                btnConnect.Text = "Disconnect";

                listStatus.Items[0].Text = "Status: Connecting...";
            }
            else
            {
                Cliente.Disconnect();
                btnConnect.Text = "Connect";
            }
        }

        private void btnSearch_Click(object sender, EventArgs e)
        {
            Search(txtQuery.Text);
        }

        private void Search(string query)
        {
            if (query.Length >= 3)
            {
                Cliente.StartSearch(query);
                listSearch.Items.Clear();
            }
            else
                MessageBox.Show("Keywords for search must contain more than 3 characters.", "Search Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }

        private void btnRandPort_Click(object sender, EventArgs e)
        {
            Random rndPort = new Random();

            txtPort.Text = (rndPort.Next() % 40000 + 10000).ToString();
        }

        private void btnSelectIncoming_Click(object sender, EventArgs e)
        {
            DialogResult result = fbwIncoming.ShowDialog();

            if (result == DialogResult.OK)
            {
                txtDirectory.Text = fbwIncoming.SelectedPath;
            }
        }

        private void Main_FormClosed(object sender, FormClosedEventArgs e)
        {
            Cliente.Disconnect();

            TimerVizinhos.Stop();

            // Save Known Users
            List<Vizinho> Conhecidos;
            List<Connection> Conectados;
            String KnownUsers = Settings.Instance().GetValue(Settings.Setting.KnownUsers);

            // Parse already known users
            List<Vizinho> Users = new List<Vizinho>();

            foreach (String User in KnownUsers.Split(';'))
            {
                if (User.Length == 0)
                    continue;

                String[] UserData = User.Split(',');

                Vizinho viz = new Vizinho();
                viz.Name = UserData[0];
                viz.IP = new IPEndPoint(IPAddress.Parse(UserData[1]), Convert.ToInt32(UserData[2]));

                Users.Add(viz);
            }

            Cliente.Vizinhos(out Conhecidos, out Conectados);

            foreach (Vizinho viz in Conhecidos)
            {
                for (int i = 0; i < Users.Count; i++)
                {
                    Vizinho known = Users[i];
                    if (known.IP.Equals(viz.IP))
                    {
                        Users.Remove(known);
                    }
                }

                Users.Add(viz);
            }

            KnownUsers = "";
            foreach (Vizinho viz in Users)
            {
                KnownUsers += viz.Name.TrimEnd('\0') + "," + viz.IP.Address.ToString() + "," + viz.IP.Port + ";";
            }

            Settings.Instance().SetValue(Settings.Setting.KnownUsers, KnownUsers);

            Settings.Instance().Save();
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            Settings sett = Settings.Instance();

            try
            {
                IPAddress.Parse(txtBootstrapIP.Text);

                if (Directory.Exists(txtDirectory.Text) == false)
                    throw new DirectoryNotFoundException();

                sett.SetValue(Settings.Setting.IncomingDirectory, txtDirectory.Text);
                sett.SetValue(Settings.Setting.IncomingPort, txtPort.Text);
                sett.SetValue(Settings.Setting.RandomizeIncomingPort, chkRandomize.Checked.ToString());
                sett.SetValue(Settings.Setting.UserName, txtName.Text);
                sett.SetValue(Settings.Setting.DefaultAddress, txtBootstrapIP.Text);
                sett.SetValue(Settings.Setting.DefaultPort, txtBootstrapPort.Text);

                if (rbtnUsers.Checked)
                    sett.SetValue(Settings.Setting.ConnectionMethod, "KnownUsers");
                else
                    sett.SetValue(Settings.Setting.ConnectionMethod, "DefaultUser");

                sett.Save();

                Cliente.Diretorio = txtDirectory.Text;
            }
            catch (DirectoryNotFoundException ex)
            {
                MessageBox.Show("Sharring directory does not exists.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (FormatException ex)
            {
                MessageBox.Show("Bootstrap IP address is invalid.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            
        }

        private void TimerVizinhos_Tick(object sender, EventArgs e)
        {
            List<Vizinho> Conhecidos;
            List<Connection> Conectados;

            Cliente.Vizinhos(out Conhecidos, out Conectados);

            listVizinhos.Items.Clear();

            string[] Item = new string[3];
            foreach (Vizinho viz in Conhecidos)
            {
                Color itemColor = Color.Black;

                Item[0] = viz.Name;
                Item[1] = viz.IP.ToString();

                switch (viz.CurrentStatus)
                {
                    case Vizinho.Status.Active:
                        Item[2] = "Active";
                        itemColor = Color.Green;
                        break;
                    case Vizinho.Status.Connected:
                        Item[2] = "Connected";
                        itemColor = Color.Blue;
                        break;
                    case Vizinho.Status.Known:
                        Item[2] = "Known";
                        itemColor = Color.Orange;
                        break;
                    case Vizinho.Status.Dead:
                        Item[2] = "Dead";
                        itemColor = Color.Red;
                        break;
                }

                ListViewItem listItem = new ListViewItem(Item);

                listItem.ForeColor = itemColor;

                listVizinhos.Items.Add(listItem);
            }

            if (Conectados.Count > 0)
            {
                listStatus.Items[0].Text = "Status: Connected";
                listStatus.Items[1].Text = "Username: " + Cliente.Name;
                listStatus.Items[2].Text = "IP: " + Cliente.IP.Address;
                listStatus.Items[3].Text = "Port: " + Cliente.IP.Port;
            }

            //--------------------------------------------------------------------------
            // Search Results
            //--------------------------------------------------------------------------
            RemoteFile[] Results;
            SortedResults = new List<SearchResult>();

            Results = Cliente.SearchResults();

            if (Results.Length > 0)
            {
                /* Para cada resultado, verifica se ele já está na lista SortedResults
                 * Caso já esteja, incrementa o Availability do arquivo.
                 * Caso não esteja, adiciona.
                 */
                foreach (RemoteFile result in Results)
                {
                    bool present = false;

                    if (result.Owner == null)
                        continue;

                    foreach (SearchResult sorted_result in SortedResults)
                    {
                        bool equal = true;
                        for (int k = 0; k < 20; k++)
                        {
                            if (result.Hash[k] != sorted_result.Hash[k])
                            {
                                equal = false;
                                break;
                            }
                        }

                        if (result.Length == sorted_result.Size && equal)
                        {
                            bool exists = false;
                            foreach (RemoteFile source in sorted_result.Sources)
                                if (source.Owner.IP.Equals(result.Owner.IP))
                                    exists = true;

                            present = true;

                            if (!exists)
                            {
                                sorted_result.Sources.Add(result);
                                sorted_result.Availability = sorted_result.Availability + 1;
                            }
                        }
                    }

                    if (!present)
                    {
                        SearchResult CurrentResult = new SearchResult();
                        CurrentResult.Availability = 1;
                        CurrentResult.Hash = result.Hash;
                        CurrentResult.Name = result.Name;
                        CurrentResult.Size = result.Length;
                        CurrentResult.Sources.Add(result);

                        if (result.Length < 1024)
                            CurrentResult.SizeString = result.Length.ToString() + " B";
                        else if (result.Length < 1024 * 1024)
                            CurrentResult.SizeString = (result.Length / 1024.0).ToString("#,#.00#;(#,#.00#)") + " KB";
                        else if (result.Length < 1024 * 1024 * 1024)
                            CurrentResult.SizeString = (result.Length / (1024.0 * 1024.0)).ToString("#,#.00#;(#,#.00#)") + " MB";
                        else
                            CurrentResult.SizeString = (result.Length / (1024.0 * 1024.0 * 1024.0)).ToString("#,#.00#;(#,#.00#)") + " GB";

                        if (CurrentResult.Name.EndsWith("mp3") ||
                            CurrentResult.Name.EndsWith("wav"))
                            CurrentResult.Type = "Audio";
                        else if (CurrentResult.Name.EndsWith("txt") ||
                            CurrentResult.Name.EndsWith("doc") ||
                            CurrentResult.Name.EndsWith("docx") ||
                            CurrentResult.Name.EndsWith("pdf"))
                            CurrentResult.Type = "Document";
                        else if (CurrentResult.Name.EndsWith("avi") ||
                            CurrentResult.Name.EndsWith("mpg") ||
                            CurrentResult.Name.EndsWith("mpeg") ||
                            CurrentResult.Name.EndsWith("mov"))
                            CurrentResult.Type = "Video";
                        else if (CurrentResult.Name.EndsWith("jpg") ||
                            CurrentResult.Name.EndsWith("jpeg") ||
                            CurrentResult.Name.EndsWith("bmp") ||
                            CurrentResult.Name.EndsWith("tif"))
                            CurrentResult.Type = "Image";
                        else
                            CurrentResult.Type = "Other";

                        SortedResults.Add(CurrentResult);
                    }
                }

                /* Para cada resultado da lista SortedResults, adiciona na lista listSearch */
                int i, j;

                for (i = 0, j = 0; i < SortedResults.Count; i++)
                {
                    SearchResult sorted_result = SortedResults[i];

                    if (j < listSearch.Items.Count)
                    {
                        ListViewItem listItem = listSearch.Items[j];

                        if (!(listItem.Text == sorted_result.Name &&
                            listItem.SubItems[3].Text == sorted_result.SizeString))
                        {
                            string[] item = {sorted_result.Name,
                                        sorted_result.Availability.ToString(),
                                        sorted_result.Type,
                                        sorted_result.SizeString};

                            // Check if file exists and paint it green
                            Color itemColor = Color.Black;

                            if (Cliente.FileExists(sorted_result.Hash, sorted_result.Size))
                                itemColor = Color.Green;

                            ListViewItem NewListItem = new ListViewItem(item);
                            NewListItem.ForeColor = itemColor;

                            listSearch.Items.Insert(j, NewListItem);
                        }
                    }
                    else
                    {
                        string[] item = {sorted_result.Name,
                                        sorted_result.Availability.ToString(),
                                        sorted_result.Type,
                                        sorted_result.SizeString};

                        // Check if file exists and paint it green
                        Color itemColor = Color.Black;

                        if (Cliente.FileExists(sorted_result.Hash, sorted_result.Size))
                            itemColor = Color.Green;

                        ListViewItem NewListItem = new ListViewItem(item);
                        NewListItem.ForeColor = itemColor;

                        listSearch.Items.Insert(j, NewListItem);
                    }

                    j++;

                }

                while (j < listSearch.Items.Count)
                {
                    listSearch.Items.RemoveAt(j);
                }
            }

            //-------------------------------
            TransfersCollection transfers = Cliente.GetTransfersCollection();

            listDownload.Items.Clear();

            foreach (DownloadItem item in transfers.DownloadCollection)
            {
                string[] listItem = {item.Name,
                                           LengthToString(item.Length),
                                           "Downloading",
                                           (item.Completed * 100).ToString("#.00") + "%",
                                           LengthToString((long)item.Speed) + "/s",
                                           item.Neighbors.Length.ToString()
                                        };

                listDownload.Items.Add(new ListViewItem(listItem));

                foreach (NeighborItem n in item.Neighbors)
                {
                    string[] listItem2 = {"    " + n.Name + "(" + n.Address.ToString() + ")",
                                           "",
                                           "Transfering",
                                           "",
                                           LengthToString((long)n.Speed) + "/s",
                                           ""
                                        };

                    listDownload.Items.Add(new ListViewItem(listItem2));
                }
            }

            listUpload.Items.Clear();

            foreach (UploadItem item in transfers.UploadCollection)
            {
                string[] listItem = {item.Name,
                                           LengthToString(item.Length),
                                           LengthToString((long)item.Speed) + "/s",
                                           item.Neighbor.Name + "(" + item.Neighbor.Address.ToString() + ")"
                                        };

                listUpload.Items.Add(new ListViewItem(listItem));
            }

        }

        private String LengthToString(Int64 length)
        {
            if (length < 1024)
                return length.ToString() + " B";
            else if (length < 1024 * 1024)
                return (length / 1024.0).ToString("#,#.00#;(#,#.00#)") + " KB";
            else if (length < 1024 * 1024 * 1024)
                return (length / (1024.0 * 1024.0)).ToString("#,#.00#;(#,#.00#)") + " MB";
            else
                return (length / (1024.0 * 1024.0 * 1024.0)).ToString("#,#.00#;(#,#.00#)") + " GB";
        }

        private void tabControl1_Selected(object sender, TabControlEventArgs e)
        {
            if (e.TabPage.Text == "Settings")
            {
                Settings sett = Settings.Instance();

                txtDirectory.Text = sett.GetValue(Settings.Setting.IncomingDirectory);

                txtName.Text = sett.GetValue(Settings.Setting.UserName);
                Cliente.Name = txtName.Text;

                if (sett.GetValue(Settings.Setting.RandomizeIncomingPort) == "True")
                    chkRandomize.Checked = true;
                else
                    chkRandomize.Checked = false;

                txtPort.Text = sett.GetValue(Settings.Setting.IncomingPort);

                txtBootstrapPort.Text = sett.GetValue(Settings.Setting.DefaultPort);
                txtBootstrapIP.Text = sett.GetValue(Settings.Setting.DefaultAddress);

                string ConnMethod = sett.GetValue(Settings.Setting.ConnectionMethod);

                if (ConnMethod == "DefaultUser")
                {
                    rbtnIP.Checked = true;
                }
                else if (ConnMethod == "KnownUsers")
                {
                    rbtnUsers.Checked = true;
                }
            }
        }

        private void splitterTransfer_SplitterMoved(object sender, SplitterEventArgs e)
        {

        }

        private delegate void WriteLogTextboxDelegate(string text);

        public void WriteToLog(string text)
        {
            try
            {
                Invoke(new WriteLogTextboxDelegate(WriteToLogMethod), text);
            }
            catch (Exception e)
            {
            }
        }

        private void WriteToLogMethod(string text)
        {
            txtLog.Text += text;
            txtLog.Select(txtLog.Text.Length, 0);
            txtLog.ScrollToCaret();
        }

        public void ProtocolConnected()
        {
            listStatus.Items[0].Text = "Status: Connected";
        }

        private void txtQuery_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)Keys.Enter)
                Search(txtQuery.Text);
        }

        private void listSearch_DoubleClick(object sender, EventArgs e)
        {
            if (listSearch.SelectedItems.Count > 0)
            {
                listSearch.SelectedItems[0].ForeColor = Color.Green;
                Cliente.StartDownload(SortedResults[listSearch.SelectedItems[0].Index].Sources.ToArray());
            }
        }
    }
}
